package org.artifact.core.db;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import org.artifact.core.cache.CacheEntry;
import org.artifact.core.context.bytebuf.IByteBuff;
import org.artifact.core.context.bytebuf.IByteBuffFactory;
import org.artifact.core.lang.RedisScript;
import org.artifact.core.plugin.jedis.JedisPlugin;
import org.artifact.core.server.ServerContext;
import org.artifact.core.server.ServerPlugins;
import org.artifact.core.util.BitUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public abstract class JedisDao<T extends AbstractEntity<T>> extends AbstractDao<T> {

    static final Log log = LogFactory.get(JedisDao.class);

    protected Jedis getJedis() {
        return ServerPlugins.me().getPlugin(JedisPlugin.class).getJedis();
    }

    static int PIPELINE_LIMIT = 512;

    @Override
    protected boolean onSave(T t) {
        Jedis jedis = getJedis();
        try {
            IByteBuffFactory factory = ServerContext.me().getByteBuffFactory();
            IByteBuff byteBuff = factory.allocate();
            Map map = (Map) t.toSave().write();
            byteBuff.writeMap(map);
            RedisScript.put(jedis, getTableName(), getDBName() + "_index_value", t.pk(), byteBuff.array(), t.getIndexKey());
        } finally {
            jedis.close();
        }
        return true;
    }

    @Override
    protected boolean onRemove(T t) {
        Jedis jedis = getJedis();
        try {
            RedisScript.remove(jedis, getTableName(), getDBName() + "_index_value", t.pk());
        } finally {
            jedis.close();
        }
        return true;
    }

    @Override
    protected boolean onSave(T... t) {
        Jedis jedis = getJedis();
        Pipeline p = jedis.pipelined();
        try {
            IByteBuffFactory factory = ServerContext.me().getByteBuffFactory();
            IByteBuff byteBuff = factory.allocate();
            int i = 0;
            for (T t1 : t) {
                // 预防管道数据过大，造成内存溢出
                if (i % PIPELINE_LIMIT == 0 && i != 0) {
                    p.clear();
                }
                byteBuff.clear();
                Map map = (Map) t1.toSave().write();
                byteBuff.writeMap(map);
                RedisScript.put(p, getTableName(), getDBName() + "_index_value", t1.pk(), byteBuff.array(), t1.getIndexKey());
                i++;
            }
            p.clear();
        } finally {
            jedis.close();
        }
        return true;
    }

    @Override
    protected boolean onRemove(T... t) {
        Jedis jedis = getJedis();
        Pipeline p = jedis.pipelined();
        try {
            for (T t1 : t) {
                RedisScript.remove(p, getTableName(), getDBName() + "_index_value", t1.pk());
            }
            p.sync();
        }finally {
            jedis.close();
        }
        return true;
    }

    @Override
    protected boolean onSave(Collection<T> coll) {
        Jedis jedis = getJedis();
        Pipeline p = jedis.pipelined();
        try {
            IByteBuffFactory factory = ServerContext.me().getByteBuffFactory();
            IByteBuff byteBuff = factory.allocate();
            int i = 0;
            for (T t1 : coll) {
                // 预防管道数据过大，造成内存溢出
                if (i % PIPELINE_LIMIT == 0 && i != 0) {
                    p.clear();
                }
                byteBuff.clear();
                Map map = (Map) t1.toSave().write();
                byteBuff.writeMap(map);
                RedisScript.put(p, getTableName(), getDBName() + "_index_value", t1.pk(), byteBuff.array(), t1.getIndexKey());
                i++;
            }
            p.clear();
        }  finally {
            jedis.close();
        }
        return true;
    }

    @Override
    protected boolean onRemove(Collection<T> coll) {
        Jedis jedis = getJedis();
        Pipeline p = jedis.pipelined();
        try {
            for (T t1 : coll) {
                RedisScript.remove(p, getTableName(), getDBName() + "_index_value", t1.pk());
            }
//            p.sync();
            p.clear();
        }finally {
            jedis.close();
        }
        return true;
    }

//    @Override
//    protected boolean finishSaveCache() {
//        Jedis jedis = getJedis();
//        Pipeline p = jedis.pipelined();
//        try {
//            IByteBuffFactory factory = ServerContext.me().getByteBuffFactory();
//            IByteBuff byteBuff = factory.allocate();
//            int i = 0;
//            for (Entry<Object, T> e : cachedUpdates.entrySet()) {
//                // 预防管道数据过大，造成内存溢出
//                if (i % PIPELINE_LIMIT == 0 && i != 0) {
//                    p.clear();
//                }
//                byteBuff.clear();
//                T bean = e.getValue();
//                Map map = (Map) bean.write();
//                byteBuff.writeMap(map);
//                RedisScript.put(p, getTableName(), getDBName() + "_index_value", e.getKey(), byteBuff.array(), bean.getIndexKey());
//                i++;
//            }
//            p.clear();
//            return true;
//        } finally {
//            jedis.close();
//        }
//    }
//
//    @Override
//    protected boolean finishDeleteCache() {
//        Jedis jedis = getJedis();
//        Pipeline p = jedis.pipelined();
//        try {
//            for (Entry<Object, T> e : cachedDeletes.entrySet()) {
//                RedisScript.remove(p, getTableName(), getDBName() + "_index_value", e.getKey());
//            }
//            p.sync();
//            return true;
//        }finally {
//            jedis.close();
//        }
//    }

    @Override
    public boolean existsTable() {
        Jedis jeids = getJedis();
        try {
            return jeids.exists(toBytes(getTableName()));
        } finally {
            jeids.close();
        }
    }

    public boolean createTable() {
        return true;
    }

    @Override
    public List<T> findAll() {
        return resultList(() -> {
            return getCache().getAllOrFunction(()->{
                List<CacheEntry<Object, T>> cacheEntrys = new ArrayList<>();
                Jedis jedis = getJedis();
                IByteBuffFactory factory = ServerContext.me().getByteBuffFactory();
                try {
                    Map<byte[], byte[]> all = jedis.hgetAll(toBytes(getTableName()));

                    Map<Integer, Object> map = null;
                    IByteBuff byteBuff = null;
                    for (Entry<byte[], byte[]> e : all.entrySet()) {
                        byteBuff = factory.wrap(e.getValue());
                        map = byteBuff.readMap();
                        if (map != null) {
                            T entity = createEntity().read(map);
                            cacheEntrys.add(new CacheEntry<>(entity.pk(),entity));
                        }
                    }
                } finally {
                    jedis.close();
                }
                return cacheEntrys;
            });
        }, t -> true);
    }

    @Override
    public T findByPrimaryKey(Object primaryKey) {
        return resultFirst(() -> {
            return getCache().getOrFunction(primaryKey,k -> {
                Jedis jedis = getJedis();
                IByteBuffFactory factory = ServerContext.me().getByteBuffFactory();
                IByteBuff byteBuff = null;
                try {
                    byte[] data = jedis.hget(toBytes(getTableName()), toBytes(k));
                    if (data != null) {
                        byteBuff = factory.wrap(data);
                        Map<Integer, Object> map = byteBuff.readMap();
                        return createEntity().read(map);
                    }
                } finally {
                    byteBuff = null;
                    jedis.close();
                }

                return null;
            });
        }, t -> ObjectUtil.equal(t.pk(), primaryKey));
    }

    public byte[] toBytes(Object obj) {
        byte[] bytes = null;
        if (obj instanceof String) {
            String v = (String) obj;
            try {
                bytes = v.getBytes(CharsetUtil.UTF_8);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } else if (obj instanceof Integer) {
            int v = (int) obj;
            bytes = new byte[4];
            BitUtil.putInt(bytes, 0, v);
        } else if (obj instanceof Long) {
            long v = (long) obj;
            bytes = new byte[8];
            BitUtil.putLong(bytes, 0, v);
        }
        return bytes;
    }
}
